#if !defined(FIXED_ARRAY_H_)
#define FIXED_ARRAY_H_
#include <array>
#include <exception>
#include <type_traits>

namespace autodrive {
namespace perception {
template <typename _Tp, std::size_t _Nm>
struct fixed_array {
  typedef _Tp value_type;
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef value_type* iterator;
  typedef const value_type* const_iterator;
  typedef std::size_t size_type;
  typedef std::ptrdiff_t difference_type;
  typedef std::reverse_iterator<iterator> reverse_iterator;
  typedef std::reverse_iterator<const_iterator> const_reverse_iterator;

  // Support for zero-sized arrays mandatory.
  typedef _GLIBCXX_STD_C::__array_traits<_Tp, _Nm> _AT_Type;

  /// @brief get size of fixed array
  /// @return size
  constexpr size_type size() const noexcept { return number_; }

  /// @brief get full status
  /// @return full status
  constexpr bool full() const noexcept { return size() == capacity(); }

  /// @brief get capacity of fixed array
  /// @return capacity
  constexpr size_type capacity() const noexcept { return _Nm; }

  /// @brief get is empty status
  /// @return empty
  constexpr bool empty() const noexcept { return size() == 0; }

  _GLIBCXX17_CONSTEXPR reference at(size_type __n) {
    if (__n >= size()) {
      std::__throw_out_of_range_fmt(__N("fixed_array::at: __n (which is %zu) "
                                        ">= size()(which is %zu)"),
                                    __n, size());
    }

    return _AT_Type::_S_ref(_M_elems_, __n);
  }

  constexpr const_reference at(size_type __n) const noexcept(false) {
    // Result of conditional expression must be an lvalue so use
    // boolean ? lvalue : (throw-expr, lvalue)
    return __n < size() ? _AT_Type::_S_ref(_M_elems_, __n)
                        : (std::__throw_out_of_range_fmt(
                               __N("fixed_array::at: __n (which is %zu) "
                                   ">= size() (which is %zu)"),
                               __n, size()),
                           _AT_Type::_S_ref(_M_elems_, 0));
  }

  /// @brief element access
  _GLIBCXX17_CONSTEXPR reference operator[](size_type __n) noexcept(false) {
    if (__n >= size()) {
      std::__throw_out_of_range_fmt(__N("fixed_array::at: __n (which is %zu) "
                                        ">= size()(which is %zu)"),
                                    __n, size());
    }
    return at(__n);
  }

  constexpr const_reference operator[](size_type __n) const noexcept(false) {
    if (__n >= size()) {
      std::__throw_out_of_range_fmt(__N("fixed_array::at: __n (which is %zu) "
                                        ">= size()(which is %zu)"),
                                    __n, size());
    }
    return at(__n);
  }

  /// @brief resize fix array
  /// @param __n size
  /// @throw out of range
  void resize(size_type __n) noexcept(false) {
    if (__n > _Nm) {
      std::__throw_out_of_range_fmt(__N("fixed_array::resize:__n(which is %zu) "
                                        "> capacity() (which is %zu)"),
                                    __n, _Nm);
    }
    number_ = __n;
  }

  /// @brief add size()+1
  void add(const_reference __u) noexcept(false) {
    if (number_ >= _Nm) {
      std::__throw_out_of_range_fmt(
          __N("fixed_array::at: number_ (which is %zu) "
              ">= capacity() (which is %zu)"),
          number_, _Nm);
    }
    _M_elems_[number_] = __u;
    ++number_;
  }

  /// @brief clear items
  void clear() noexcept { number_ = 0; }

  void fill(const value_type& __u) { std::fill_n(begin(), size(), __u); }

  void swap(fixed_array& __other) noexcept(
      _AT_Type::_Is_nothrow_swappable::value) {
    std::swap_ranges(begin(), end(), __other.begin());
  }

  // Iterators.
  _GLIBCXX17_CONSTEXPR iterator begin() noexcept { return iterator(data()); }

  _GLIBCXX17_CONSTEXPR const_iterator begin() const noexcept {
    return const_iterator(data());
  }

  _GLIBCXX17_CONSTEXPR iterator end() noexcept {
    return iterator(data() + size());
  }

  _GLIBCXX17_CONSTEXPR const_iterator end() const noexcept {
    return const_iterator(data() + size());
  }

  _GLIBCXX17_CONSTEXPR reverse_iterator rbegin() noexcept {
    return reverse_iterator(end());
  }

  _GLIBCXX17_CONSTEXPR const_reverse_iterator rbegin() const noexcept {
    return const_reverse_iterator(end());
  }

  _GLIBCXX17_CONSTEXPR reverse_iterator rend() noexcept {
    return reverse_iterator(begin());
  }

  _GLIBCXX17_CONSTEXPR const_reverse_iterator rend() const noexcept {
    return const_reverse_iterator(begin());
  }

  _GLIBCXX17_CONSTEXPR const_iterator cbegin() const noexcept {
    return const_iterator(data());
  }

  _GLIBCXX17_CONSTEXPR const_iterator cend() const noexcept {
    return const_iterator(data() + size());
  }

  _GLIBCXX17_CONSTEXPR const_reverse_iterator crbegin() const noexcept {
    return const_reverse_iterator(end());
  }

  _GLIBCXX17_CONSTEXPR const_reverse_iterator crend() const noexcept {
    return const_reverse_iterator(begin());
  }

  _GLIBCXX17_CONSTEXPR reference front() noexcept { return *begin(); }

  constexpr const_reference front() const noexcept {
    return _AT_Type::_S_ref(_M_elems_, 0);
  }

  _GLIBCXX17_CONSTEXPR reference back() noexcept {
    return size() > 0 ? *(end() - 1) : *end();
  }

  constexpr const_reference back() const noexcept {
    return size() > 0 ? _AT_Type::_S_ref(_M_elems_, size() - 1)
                      : _AT_Type::_S_ref(_M_elems_, 0);
  }

  _GLIBCXX17_CONSTEXPR pointer data() noexcept {
    return _AT_Type::_S_ptr(_M_elems_);
  }

  _GLIBCXX17_CONSTEXPR const_pointer data() const noexcept {
    return _AT_Type::_S_ptr(_M_elems_);
  }

 private:
  typename _AT_Type::_Type _M_elems_;
  /// @brief number
  std::size_t number_;
};

#if __cpp_deduction_guides >= 201606
template <typename _Tp, typename... _Up>
fixed_array(_Tp, _Up...)
    -> fixed_array<std::enable_if_t<(std::is_same_v<_Tp, _Up> && ...), _Tp>,
                   1 + sizeof...(_Up)>;
#endif

// Array comparisons.
template <typename _Tp, std::size_t _Nm>
inline bool operator==(const fixed_array<_Tp, _Nm>& __one,
                       const fixed_array<_Tp, _Nm>& __two) {
  return std::equal(__one.begin(), __one.end(), __two.begin());
}

template <typename _Tp, std::size_t _Nm>
inline bool operator!=(const fixed_array<_Tp, _Nm>& __one,
                       const fixed_array<_Tp, _Nm>& __two) {
  return !(__one == __two);
}

template <typename _Tp, std::size_t _Nm>
inline bool operator<(const fixed_array<_Tp, _Nm>& __a,
                      const fixed_array<_Tp, _Nm>& __b) {
  return std::lexicographical_compare(__a.begin(), __a.end(), __b.begin(),
                                      __b.end());
}

template <typename _Tp, std::size_t _Nm>
inline bool operator>(const fixed_array<_Tp, _Nm>& __one,
                      const fixed_array<_Tp, _Nm>& __two) {
  return __two < __one;
}

template <typename _Tp, std::size_t _Nm>
inline bool operator<=(const fixed_array<_Tp, _Nm>& __one,
                       const fixed_array<_Tp, _Nm>& __two) {
  return !(__one > __two);
}

template <typename _Tp, std::size_t _Nm>
inline bool operator>=(const fixed_array<_Tp, _Nm>& __one,
                       const fixed_array<_Tp, _Nm>& __two) {
  return !(__one < __two);
}

// Specialized algorithms.
template <typename _Tp, std::size_t _Nm>
inline
#if __cplusplus > 201402L || !defined(__STRICT_ANSI__)  // c++1z or gnu++11
    // Constrained free swap overload, see p0185r1
    typename std::enable_if<
        _GLIBCXX_STD_C::__array_traits<_Tp, _Nm>::_Is_swappable::value>::type
#else
    void
#endif
    swap(fixed_array<_Tp, _Nm>& __one,
         fixed_array<_Tp, _Nm>& __two) noexcept(noexcept(__one.swap(__two))) {
  __one.swap(__two);
}

#if __cplusplus > 201402L || !defined(__STRICT_ANSI__)  // c++1z or gnu++11
template <typename _Tp, std::size_t _Nm>
typename std::enable_if<
    !_GLIBCXX_STD_C::__array_traits<_Tp, _Nm>::_Is_swappable::value>::type
swap(fixed_array<_Tp, _Nm>&, fixed_array<_Tp, _Nm>&) = delete;
#endif

template <std::size_t _Int, typename _Tp, std::size_t _Nm>
constexpr _Tp& get(fixed_array<_Tp, _Nm>& __arr) noexcept {
  static_assert(_Int < _Nm, "array index is within bounds");
  return _GLIBCXX_STD_C::__array_traits<_Tp, _Nm>::_S_ref(__arr._M_elems, _Int);
}

template <std::size_t _Int, typename _Tp, std::size_t _Nm>
constexpr _Tp&& get(fixed_array<_Tp, _Nm>&& __arr) noexcept {
  static_assert(_Int < _Nm, "array index is within bounds");
  return std::move(_GLIBCXX_STD_C::get<_Int>(__arr));
}

template <std::size_t _Int, typename _Tp, std::size_t _Nm>
constexpr const _Tp& get(const fixed_array<_Tp, _Nm>& __arr) noexcept {
  static_assert(_Int < _Nm, "array index is within bounds");
  return _GLIBCXX_STD_C::__array_traits<_Tp, _Nm>::_S_ref(__arr._M_elems, _Int);
}

template <std::size_t _Int, typename _Tp, std::size_t _Nm>
constexpr const _Tp&& get(const fixed_array<_Tp, _Nm>&& __arr) noexcept {
  static_assert(_Int < _Nm, "array index is within bounds");
  return std::move(_GLIBCXX_STD_C::get<_Int>(__arr));
}
}  // namespace perception
}  // namespace autodrive

#endif  // FIXED_ARRAY_H_
